'    Copyright 2008 Jan Molnar <jan.molnar@abds.de>
'
'    This file is part of JCQ.

'    JCQ is free software: you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation, either version 2 of the License, or
'    (at your option) any later version.

'    JCQ is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.

'    You should have received a copy of the GNU General Public License
'    along with JCQ. If not, see <http://www.gnu.org/licenses/>.

Public Class IcqStorageService
    Inherits ContextService

    Implements Interfaces.IStorageService

    Private ReadOnly _StoredContacts As New Core.KeyedNotifiyingCollection(Of String, Interfaces.IContact)(Function(c) c.Identifier)
    Private ReadOnly _AllContacts As New Core.KeyedNotifiyingCollection(Of String, Interfaces.IContact)(Function(c) c.Identifier)
    Private ReadOnly _Groups As New Core.KeyedNotifiyingCollection(Of String, Interfaces.IGroup)(Function(g) g.Identifier)
    Private _MasterGroup As Interfaces.IGroup

    Private _MaxSSIItemId As Integer

    Private ReadOnly waitSSIAkk As System.Threading.ManualResetEvent = New System.Threading.ManualResetEvent(False)
    Private codeSSIAkk As DataTypes.SSIActionResultCode

    Public Sub New(ByVal context As Interfaces.IContext)
        MyBase.New(context)

        Dim connector As IcqConnector = TryCast(context.GetService(Of Interfaces.IConnector)(), IcqConnector)

        If connector Is Nothing Then Throw New InvalidCastException("Context Connector Service must be of Type IcqConnector")

        _AllContacts.Add(context.Identity)

        AddHandler context.Identity.PropertyChanged, AddressOf OnContactPropertyChanged

        connector.RegisterSnacHandler(&H13, &H6, New Action(Of DataTypes.Snac1306)(AddressOf AnalyseSnac1306))
        connector.RegisterSnacHandler(&H13, &H8, New Action(Of DataTypes.Snac1308)(AddressOf AnalyseSnac1308))
        connector.RegisterSnacHandler(&H13, &HA, New Action(Of DataTypes.Snac130A)(AddressOf AnalyseSnac130A))
        connector.RegisterSnacHandler(&H13, &HE, New Action(Of DataTypes.Snac130E)(AddressOf AnalyseSnac130E))
        connector.RegisterSnacHandler(&H13, &HF, New Action(Of DataTypes.Snac130F)(AddressOf AnalyseSnac130F))
    End Sub

    Public Function GetContactByIdentifier(ByVal identifier As String) As Interfaces.IContact Implements Interfaces.IStorageService.GetContactByIdentifier
        Dim contact As Interfaces.IContact

        If Not _AllContacts.Contains(identifier) Then
            contact = New IcqContact(identifier, identifier)

            _AllContacts.Add(contact)

            AddHandler contact.PropertyChanged, AddressOf OnContactPropertyChanged
        Else
            contact = _AllContacts(identifier)
        End If

        Return contact
    End Function

    Public Function GetGroupByIdentifier(ByVal identifier As String) As Interfaces.IGroup Implements Interfaces.IStorageService.GetGroupByIdentifier
        If _Groups.Contains(identifier) Then Return _Groups(identifier) Else Return Nothing
    End Function

    Private Function GetGroupByGroupId(ByVal groupId As Integer) As Interfaces.IGroup
        For Each group As Interfaces.IGroup In _Groups
            If Convert.ToInt32(group.Attributes("GroupId")) = groupId Then Return group
        Next
    End Function

    Public Function GetNextSSIItemId() As Integer
        Return Threading.Interlocked.Increment(_MaxSSIItemId)
    End Function

#Region " Privacy Lists "
    Private _VisibleList As New Core.NotifyingCollection(Of Interfaces.IContact)
    Private _ReadOnlyVisibleList As New Core.ReadOnlyNotifyingCollection(Of Interfaces.IContact)(_VisibleList)

    Public ReadOnly Property VisibleList() As Core.ReadOnlyNotifyingCollection(Of Interfaces.IContact)
        Get
            Return _ReadOnlyVisibleList
        End Get
    End Property

    Private _InvisibleList As New Core.NotifyingCollection(Of Interfaces.IContact)
    Private _ReadOnlyInvisibleList As New Core.ReadOnlyNotifyingCollection(Of Interfaces.IContact)(_InvisibleList)

    Public ReadOnly Property InvisibleList() As Core.ReadOnlyNotifyingCollection(Of Interfaces.IContact)
        Get
            Return _ReadOnlyInvisibleList
        End Get
    End Property

    Private _IgnoreList As New Core.NotifyingCollection(Of Interfaces.IContact)
    Private _ReadOnlyIgnoreList As New Core.ReadOnlyNotifyingCollection(Of Interfaces.IContact)(_IgnoreList)

    Public ReadOnly Property IgnoreList() As Core.ReadOnlyNotifyingCollection(Of Interfaces.IContact)
        Get
            Return _ReadOnlyIgnoreList
        End Get
    End Property
#End Region

    Friend Sub InnerAddContactToStorage(ByVal contact As Interfaces.IContact, ByVal group As Interfaces.IGroup)
        If Not _StoredContacts.Contains(contact.Identifier) Then _StoredContacts.Add(contact)

        DirectCast(contact, IcqContact).SetGroup(group)

        If Not contact.Group.Contacts.Contains(contact) Then contact.Group.Contacts.Add(contact)
    End Sub

    Friend Sub InnerAddContactToVisibleList(ByVal contact As Interfaces.IContact)
        If Not _VisibleList.Contains(contact) Then _VisibleList.Add(contact)
    End Sub

    Friend Sub InnerAddContactToInvisibleList(ByVal contact As Interfaces.IContact)
        If Not _InvisibleList.Contains(contact) Then _InvisibleList.Add(contact)
    End Sub

    Friend Sub InnerAddContactToIgnoreList(ByVal contact As Interfaces.IContact)
        If Not _IgnoreList.Contains(contact) Then _IgnoreList.Add(contact)
    End Sub

    Friend Sub InnerRemoveContactFromStorage(ByVal contact As Interfaces.IContact)
        If Not _StoredContacts.Contains(contact.Identifier) Then _StoredContacts.Remove(contact.Identifier)

        If contact.Group.Contacts.Contains(contact) Then contact.Group.Contacts.Remove(contact)
    End Sub

    Friend Sub InnerRemoveContactFromVisibleList(ByVal contact As Interfaces.IContact)
        If _VisibleList.Contains(contact) Then _VisibleList.Remove(contact)
    End Sub

    Friend Sub InnerRemoveContactFromInvisibleList(ByVal contact As Interfaces.IContact)
        If _InvisibleList.Contains(contact) Then _InvisibleList.Remove(contact)
    End Sub

    Friend Sub InnerRemoveContactFromIgnoreList(ByVal contact As Interfaces.IContact)
        If _IgnoreList.Contains(contact) Then _IgnoreList.Remove(contact)
    End Sub

    Public Sub AddContact(ByVal contact As Interfaces.IContact, ByVal group As Interfaces.IGroup) Implements Interfaces.IStorageService.AddContact
        Dim trans As AddContactTransaction
        Dim icontact As IcqContact
        Dim igroup As IcqGroup

        icontact = DirectCast(contact, IcqContact)
        igroup = DirectCast(group, IcqGroup)

        trans = New AddContactTransaction(Me, icontact, igroup)

        CommitSSITransaction(trans)
    End Sub

    Public Sub RemoveContact(ByVal contact As Interfaces.IContact, ByVal group As Interfaces.IGroup) Implements Interfaces.IStorageService.RemoveContact
        Dim trans As RemoveContactTransaction
        Dim icontact As IcqContact

        icontact = DirectCast(contact, IcqContact)

        trans = New RemoveContactTransaction(Me, icontact)

        CommitSSITransaction(trans)
    End Sub

    Public Sub CommitSSITransaction(ByVal trans As ISSITransaction)
        Dim beginTransaction As DataTypes.Snac1311
        Dim endTransaction As DataTypes.Snac1312
        Dim item As DataTypes.Snac

        Dim transfer As IIcqDataTranferService

        ' Check wheter all prerequirements are met to commit the transaction.
        trans.Validate()

        transfer = DirectCast(Context.GetService(Of Interfaces.IConnector)(), IIcqDataTranferService)

        ' Create the transaction data
        beginTransaction = New DataTypes.Snac1311
        item = trans.CreateSnac()
        endTransaction = New DataTypes.Snac1312

        ' Send data.
        transfer.Send(beginTransaction, item, endTransaction)

        ' Wait for server response.
        If Not waitSSIAkk.WaitOne(TimeSpan.FromSeconds(5), False) Then
            Throw New TimeoutException("Server did not respond.")
        Else
            trans.OnComplete(codeSSIAkk)
        End If
    End Sub

    Public Sub AttachContact(ByVal contact As Interfaces.IContact, ByVal group As Interfaces.IGroup, ByVal stored As Boolean) Implements Interfaces.IStorageService.AttachContact
        If Not _AllContacts.Contains(contact.Identifier) Then
            _AllContacts.Add(contact)

            AddHandler contact.PropertyChanged, AddressOf OnContactPropertyChanged
        End If

        If stored Then
            If Not _StoredContacts.Contains(contact.Identifier) Then _StoredContacts.Add(contact)

            If Not group.Contacts.Contains(contact) Then group.Contacts.Add(contact)
        End If
    End Sub

    Public Sub DetachContact(ByVal contact As Interfaces.IContact, ByVal group As Interfaces.IGroup)
        If group.Contacts.Contains(contact) Then group.Contacts.Add(contact)

        If _StoredContacts.Contains(contact.Identifier) Then _StoredContacts.Add(contact)
    End Sub

    Friend Sub AnalyseSnac130E(ByVal dataIn As DataTypes.Snac130E)
        codeSSIAkk = dataIn.ActionResultCodes.First
        waitSSIAkk.Set()

        For Each code As DataTypes.SSIActionResultCode In dataIn.ActionResultCodes
            Debug.WriteLine(String.Format("SSI Change Akk: {0}", code), "IcqStorageService")
        Next
    End Sub

    Friend Sub AnalyseSnac1306(ByVal dataIn As DataTypes.Snac1306)
        Try
            _MaxSSIItemId = Math.Max(_MaxSSIItemId, dataIn.MaxItemId)

            For Each ssiGroup As DataTypes.SSIGroupRecord In dataIn.GroupRecords
                Dim identifier As String = ssiGroup.ItemName

                Dim group As IcqGroup = New IcqGroup(identifier, ssiGroup.GroupId)

                If group.GroupId = 0 Then
                    _MasterGroup = group
                Else
                    _Groups.Add(group)
                End If
            Next

            For Each x As Interfaces.IGroup In _Groups
                _MasterGroup.Groups.Add(x)
            Next

            For Each ssiContact As DataTypes.SSIBuddyRecord In dataIn.BuddyRecords
                Dim identifier As String = ssiContact.ItemName
                Dim group As Interfaces.IGroup = GetGroupByGroupId(ssiContact.GroupId)

                If Not Integer.TryParse(identifier, Nothing) Then Continue For

                Dim contact As IcqContact = DirectCast(GetContactByIdentifier(identifier), IcqContact) ' New IcqContact(identifier, ssiContact.ItemId, ssiContact.LocalScreenName.LocalScreenName, group)

                If Not contact.LastShortUserInfoRequest > DateTime.MinValue Then contact.Name = ssiContact.LocalScreenName.LocalScreenName
                contact.ItemId = ssiContact.ItemId
                contact.SetGroup(group)

                AttachContact(contact, group, True)
            Next

            For Each record As DataTypes.SSIDenyRecord In dataIn.DenyRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).DenyRecordItemId = record.ItemId

                _InvisibleList.Add(contact)
            Next

            For Each record As DataTypes.SSIPermitRecord In dataIn.PermitRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).PermitRecordItemId = record.ItemId

                _VisibleList.Add(contact)
            Next

            For Each record As DataTypes.SSIIgnoreListRecord In dataIn.IgnoreListRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).IgnoreRecordItemId = record.ItemId

                _IgnoreList.Add(contact)
            Next

            _Info = New ContactListInfo(dataIn.ItemCount, dataIn.LastChange)

            RaiseEvent ContactListActivated(Me, System.EventArgs.Empty)
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Friend Sub AnalyseSnac130F(ByVal dataIn As DataTypes.Snac130F)
        Try
            _Info = New ContactListInfo(dataIn.NumberOfItems, dataIn.ModificationDate)

            RaiseEvent ContactListActivated(Me, System.EventArgs.Empty)
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Friend Sub AnalyseSnac1308(ByVal dataIn As DataTypes.Snac1308)
        ' Sever informs the client that items have beend added to the SSI Store.
        Try
            For Each ssiGroup As DataTypes.SSIGroupRecord In dataIn.GroupRecords
                Dim identifier As String = ssiGroup.ItemName

                Dim group As IcqGroup = New IcqGroup(identifier, ssiGroup.GroupId)

                _Groups.Add(group)
            Next

            For Each ssiContact As DataTypes.SSIBuddyRecord In dataIn.BuddyRecords
                Dim identifier As String = ssiContact.ItemName
                Dim group As Interfaces.IGroup = GetGroupByGroupId(ssiContact.GroupId)

                If Not Integer.TryParse(identifier, Nothing) Then Continue For

                Dim contact As IcqContact = DirectCast(GetContactByIdentifier(identifier), IcqContact)

                If Not contact.LastShortUserInfoRequest > DateTime.MinValue Then contact.Name = ssiContact.LocalScreenName.LocalScreenName
                contact.ItemId = ssiContact.ItemId
                contact.SetGroup(group)

                AttachContact(contact, group, True)
            Next

            For Each record As DataTypes.SSIDenyRecord In dataIn.DenyRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).DenyRecordItemId = record.ItemId

                _InvisibleList.Add(contact)
            Next

            For Each record As DataTypes.SSIPermitRecord In dataIn.PermitRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).PermitRecordItemId = record.ItemId

                _VisibleList.Add(contact)
            Next

            For Each record As DataTypes.SSIIgnoreListRecord In dataIn.IgnoreListRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).IgnoreRecordItemId = record.ItemId

                _IgnoreList.Add(contact)
            Next
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Friend Sub AnalyseSnac130A(ByVal dataIn As DataTypes.Snac130A)
        ' Sever informs the client that items have beend removed from the SSI Store.

        Try
            For Each record As DataTypes.SSIDenyRecord In dataIn.DenyRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).DenyRecordItemId = 0

                _InvisibleList.Remove(contact)
            Next

            For Each record As DataTypes.SSIPermitRecord In dataIn.PermitRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).PermitRecordItemId = 0

                _VisibleList.Remove(contact)
            Next

            For Each record As DataTypes.SSIIgnoreListRecord In dataIn.IgnoreListRecords
                Dim identifier As String = record.ItemName
                Dim contact As Interfaces.IContact = GetContactByIdentifier(identifier)

                DirectCast(contact, IcqContact).IgnoreRecordItemId = 0

                _IgnoreList.Remove(contact)
            Next

            For Each ssiContact As DataTypes.SSIBuddyRecord In dataIn.BuddyRecords
                Dim identifier As String = ssiContact.ItemName
                Dim group As Interfaces.IGroup = GetGroupByGroupId(ssiContact.GroupId)

                If Not Integer.TryParse(identifier, Nothing) Then Continue For

                Dim contact As IcqContact = DirectCast(GetContactByIdentifier(identifier), IcqContact)

                DetachContact(contact, group)
            Next

            For Each ssiGroup As DataTypes.SSIGroupRecord In dataIn.GroupRecords
                Dim identifier As String = ssiGroup.ItemName

                Dim group As IcqGroup = New IcqGroup(identifier, ssiGroup.GroupId)

                _Groups.Remove(group)
            Next
        Catch ex As Exception
            Core.Kernel.Exceptions.PublishException(ex)
        End Try
    End Sub

    Public Sub AddGroup(ByVal group As Interfaces.IGroup) Implements Interfaces.IStorageService.AddGroup
        Throw New NotImplementedException
    End Sub

    Public Sub RemoveGroup(ByVal group As Interfaces.IGroup) Implements Interfaces.IStorageService.RemoveGroup
        Throw New NotImplementedException
    End Sub

    Public Sub UpdateContact(ByVal contact As Interfaces.IContact) Implements Interfaces.IStorageService.UpdateContact
        Throw New NotImplementedException
    End Sub

    Public Sub UpdateGroup(ByVal group As Interfaces.IGroup) Implements Interfaces.IStorageService.UpdateGroup
        Throw New NotImplementedException
    End Sub

    Public Event ContactListActivated(ByVal sender As Object, ByVal e As System.EventArgs) Implements Interfaces.IStorageService.ContactListActivated

    Public Function IsContactStored(ByVal contact As Interfaces.IContact) As Boolean Implements Interfaces.IStorageService.IsContactStored
        Return _StoredContacts.Contains(contact)
    End Function

    Public ReadOnly Property Contacts() As Core.Interfaces.INotifyingCollection(Of Interfaces.IContact) Implements Interfaces.IStorageService.Contacts
        Get
            'TODO: Return ReadOnly list
            Return _StoredContacts
        End Get
    End Property

    Public ReadOnly Property Groups() As Core.Interfaces.INotifyingCollection(Of Interfaces.IGroup) Implements Interfaces.IStorageService.Groups
        Get
            'TODO: Return ReadOnly list
            Return _Groups
        End Get
    End Property

    Public ReadOnly Property MasterGroup() As Interfaces.IGroup Implements Interfaces.IStorageService.MasterGroup
        Get
            Return _MasterGroup
        End Get
    End Property

    Private _Info As Interfaces.IContactListInfo

    Public Sub RegisterLocalContactList(ByVal itemCount As Integer, ByVal dateChanged As Date) Implements Interfaces.IStorageService.RegisterLocalContactList
        _Info = New ContactListInfo(itemCount, dateChanged)
        _IsFreshContactList = False
    End Sub

    Public ReadOnly Property Info() As Interfaces.IContactListInfo Implements Interfaces.IStorageService.Info
        Get
            Return _Info
        End Get
    End Property

    Public ReadOnly Property IsContactListAvailable() As Boolean Implements Interfaces.IStorageService.IsContactListAvailable
        Get
            Return _Info IsNot Nothing
        End Get
    End Property

    Private _IsFreshContactList As Boolean = True

    Public ReadOnly Property IsFreshContactList() As Boolean Implements Interfaces.IStorageService.IsFreshContactList
        Get
            Return _IsFreshContactList
        End Get
    End Property

    Public Event ContactStatusChanged(ByVal sender As Object, ByVal e As Interfaces.StatusChangedEventArgs) Implements Interfaces.IStorageService.ContactStatusChanged

    Protected Sub OnContactPropertyChanged(ByVal sender As Object, ByVal e As ComponentModel.PropertyChangedEventArgs)
        If e.PropertyName = "Status" Then
            Dim contact As Interfaces.IContact = TryCast(sender, Interfaces.IContact)

            If contact IsNot Nothing Then
                Dim args = New Interfaces.StatusChangedEventArgs(contact.Status, contact)

                RaiseEvent ContactStatusChanged(Me, args)
            End If
        End If
    End Sub
End Class

Public Class ContactListInfo
    Implements Interfaces.IContactListInfo

    Private _DateChanged As Date
    Private _ItemCount As Integer

    Public Sub New(ByVal itemCount As Integer, ByVal dateChanged As Date)
        _ItemCount = itemCount
        _DateChanged = dateChanged
    End Sub

    Public ReadOnly Property DateChanged() As Date Implements Interfaces.IContactListInfo.DateChanged
        Get
            Return _DateChanged
        End Get
    End Property

    Public ReadOnly Property ItemCount() As Integer Implements Interfaces.IContactListInfo.ItemCount
        Get
            Return _ItemCount
        End Get
    End Property
End Class





